// JavaScript 对每个创建的对象都会设置一个原型, 指向它的原型对象
// 当用 obj.xxx 访问一个对象属性, JS 引擎先在当前对象上查找该属性, 如果没有, 就到其原型对象上找, 如果还没有找到
// 就一直上溯到 Object.prototype 对象, 最后, 如果还没有找到, 就只能返回 undefined
var arr = [1, 2, 3];  // 例如, 创建一个 Array 对象
// 其原型链是: arr ----> Array.prototype ----> Object.prototype ----> null
// Array.prototype 定义了 indexOf()、shift() 等方法, 因此你可以在所有的 Array 对象上直接调用这些方法
function foo() { return 0; }
// 函数也是一个对象, 它的原型链是: foo ----> Function.prototype ----> Object.prototype ----> null
// 由于 Function.prototype 定义了 apply() 等方法, 因此, 所有函数都可以调用 apply() 方法

// 第二种创建对象方法: 构造函数 (第一种是 var xiaoming = {name: 'xiaoming'} 直接创建)
function Student(name) { this.name = name; this.hello = function () { console.log('Hello, ' + this.name + '!'); } }  // 构造函数首字母大写
var xiaoming = new Student('小明');  // 原型链: xiaoming ----> Student.prototype ----> Object.prototype ----> null
console.log(xiaoming.name); xiaoming.hello();  // '小明', Hello, 小明!
// 不写 new, 是一个普通函数, 返回 undefined;
// 写了 new, 变成了一个构造函数, 它绑定的 this 指向新创建的对象, 并默认返回 this, 也就是说, 不需要在最后写 return this
// 用 new Student() 创建的对象还从原型上获得了一个 constructor 属性, 它指向函数 Student 本身
console.log(xiaoming.constructor === Student.prototype.constructor);  // true
console.log(Student.prototype.constructor === Student);               // true
console.log(Object.getPrototypeOf(xiaoming) === Student.prototype);   // true
console.log(xiaoming instanceof Student);                             // true
Student.prototype.hello_1 = function () { console.log('Hello, ' + this.name + ' awesome !'); };  // 动态添加函数
xiaoming.hello_1()  // hello() 各个对象之间不是共用的, hello_1() 是共用的, 见 廖大_创建对象(用图..., 给廖大跪了)

// 将 new 封装, 一种常用的编程模式
function Student_1(props) {
    this.name = props.name || '匿名';
    this.grade = props.grade || 1;
}  // 默认值为'匿名' 和 1
Student_1.prototype.hello = function () { console.log('Hello, ' + this.name + '!'); };
function createStudent(props) { return new Student_1(props || {}) }
var xiaoming = createStudent({ name: '小明' });  // 参数可以不传
console.log(xiaoming.grade);  // 1
